home *** CD-ROM | disk | FTP | other *** search
/ MacHack 1996 / MacHack 1996.toast / Hacks / Hacks ’92 / DylanTalk / DylanTalk Source / DylanTalk.a next >
Encoding:
Text File  |  1992-06-18  |  33.5 KB  |  1,079 lines  |  [TEXT/MPS ]

  1. ;
  2. ;    DylanTalk by Dean Yu and Fred Monroe (President/Chief Technical Officer)
  3. ;
  4. ;    Copyright 1992 FGM Enterprises.  All rights reserved.
  5. ;
  6.  
  7.             include    'SysEqu.a'
  8.             include    'QuickEqu.a'
  9.             include    'ToolEqu.a'
  10.             include    'Traps.a'
  11.             include    'GestaltEqu.a'
  12.             include    'Icons.a'
  13.             include    'Sound.a'
  14.             
  15.             include    'DylanTalkEqu.a'
  16.  
  17. ;_________________________________________________________________________________________
  18. ; Installation code
  19.             
  20. InstallDylanTalk    Proc    Export
  21.             Import    DylanTalkGestalt
  22.             Import    ShowHidePatch, OldShowHide
  23.             Import    NewDialogPatch, oldNewDialog
  24.             Import    SystemTaskPatch
  25.             Import    SystemMenuPatch, oldSystemMenu
  26.             Import    HiliteControlPatch, oldHiliteControl
  27.             Import    TrackControlPatch, oldTrackControl
  28.             Import    DialogSelectPatch, oldDialogSelect
  29.             Import    OpenPersonalityFile
  30.             
  31.             tst.w    $17a
  32.             bpl.s    @noCMDKey
  33.             _Debugger
  34. @noCMDKey
  35.  
  36. ; Look for our globals.  If the 'DTlk' gestalt selector is already there, don’t install
  37. ; ourselves again.
  38.  
  39.             move.l    a0,-(sp)                    ; Save handle to init
  40.             move.l    #gestaltDylanTalk,d0
  41.             _Gestalt
  42.             cmpi.w    #gestaltDupSelectorErr,d0    ; See if globals are already installed
  43.             bne.s    @installDylanTalk            ; Go ahead and install ourselves
  44.             addq    #4,sp                        ; Pop the handle
  45.             rts
  46.             
  47. @installDylanTalk
  48.             _DetachResource                        ; Detach INIT so it sticks around.
  49.             
  50. ; Install our main patch, which parses static text items when dialog windows appear.
  51.             
  52.             move.w    #kShowHideTrapWord,d0
  53.             _GetTrapAddress ,NewTool
  54.             lea        oldShowHide,a1
  55.             move.l    a0,2(a1)
  56.             lea        ShowHidePatch,a0
  57.             move.w    #kShowHideTrapWord,d0
  58.             _SetTrapAddress ,NewTool            ; Apply our patch to ShowHide
  59.             
  60. ; Set up a patch on NewDialog which forces it to call ShowHide. That
  61. ; way we can catch alerts too…
  62.  
  63.             move.w    #kNewDialogTrapWord,d0
  64.             _GetTrapAddress ,NewTool
  65.             lea        oldNewDialog,a1
  66.             move.l    a0,2(a1)
  67.             lea        NewDialogPatch,a0
  68.             move.w    #kNewDialogTrapWord,d0
  69.             _SetTrapAddress ,NewTool            ; Apply our patch to NewDialog
  70.     
  71. ; Install patch to HiliteControl which talks when the keyboard is used to dismiss a dialog
  72.  
  73.             move.w    #kHiliteControlTrapWord,d0
  74.             _GetTrapAddress ,NewTool
  75.             lea        oldHiliteControl,a1
  76.             move.l    a0,2(a1)                    ; Save real TrackControl
  77.             lea        HiliteControlPatch,a0
  78.             move.w    #kHiliteControlTrapWord,d0
  79.             _SetTrapAddress ,NewTool
  80.  
  81. ; Install patch to TrackControl which says “Enh” if mouse is let up outside of a control.
  82.  
  83.             move.w    #kTrackControlTrapWord,d0
  84.             _GetTrapAddress ,NewTool
  85.             lea        oldTrackControl,a1
  86.             move.l    a0,2(a1)                    ; Save real TrackControl
  87.             lea        TrackControlPatch,a0
  88.             move.w    #kTrackControlTrapWord,d0
  89.             _SetTrapAddress ,NewTool
  90.  
  91. ; Install patch to DialogSelect which takes care of speaking control titles when they
  92. ; are hit.
  93.  
  94.             move.w    #kDialogSelectTrapWord,d0
  95.             _GetTrapAddress ,NewTool
  96.             lea        oldDialogSelect,a1
  97.             move.l    a0,2(a1)                    ; Save real DialogSelect
  98.             lea        DialogSelectPatch,a0
  99.             move.w    #kDialogSelectTrapWord,d0
  100.             _SetTrapAddress ,NewTool
  101.  
  102. ; Set up patch to SystemTask to install our menu
  103.  
  104.             move.w    #kSystemTaskTrapWord,d0
  105.             _GetTrapAddress ,NewTool
  106.             lea        SystemTaskPatch,a1
  107.             move.l    a0,2(a1)
  108.             move.l    a1,a0
  109.             move.w    #kSystemTaskTrapWord,d0
  110.             _SetTrapAddress ,NewTool            ; Apply our patch to SystemTask
  111.  
  112. ; Set up patch to SystemMenu which tracks selections in the personality menu
  113.  
  114.             move.w    #kSystemMenuTrapWord,d0
  115.             _GetTrapAddress ,NewTool
  116.             lea        oldSystemMenu,a1
  117.             move.l    a0,2(a1)
  118.             lea        SystemMenuPatch,a0
  119.             move.w    #kSystemMenuTrapWord,d0
  120.             _SetTrapAddress ,NewTool            ; Apply our patch to SystemMenu
  121.     
  122. ; Allocate memory for our globals
  123.  
  124.             move.l    #DylanTalkGlobals.globalSize,d0
  125.             _NewHandle ,Sys,Clear
  126.             _HLock
  127.             move.l    a0,a3                        ; Handle to our globals
  128.             move.l    (a3),a2
  129.             
  130.             move.l    #256,d0
  131.             _NewPtr    ,Sys,Clear
  132.             move.l    a0,DylanTalkGlobals.firstDialogString(a2)    ; Space for string to speak
  133.  
  134. ; Get our about box dialog.
  135.  
  136.             move.l    TheZone,-(sp)                ; Save the current zone
  137.             move.l    SysZone,TheZone                ; Make the system zone the current zone
  138.  
  139.             subq    #2,sp
  140.             move.w    #times,-(sp)                ; I know I’m supposed to get the name, but as Fred says, “This is just a hack.”
  141.             move.w    #72,-(sp)                    ; See if the biggest size we use in the About box is available.
  142.             _RealFont
  143.             tst.b    (sp)+                        ; If the size is right, make our about box available.
  144.             bz.s    @getMenuIcon                ; Otherwise, we’ll disable the item later.
  145.  
  146.             subq    #4,sp
  147.             move.w    #128,-(sp)
  148.             _GetPicture
  149.             move.l    (sp),DylanTalkGlobals.aboutPicture(a2)        ; Save pointer to about box picture
  150.             _DetachResource                        ; Detach the resource so it sticks around.
  151.             
  152. ; Get our menu, and icons, and insert it in the menu.
  153.  
  154. @getMenuIcon
  155.             subq    #4,sp
  156.             move.l    sp,a0                        ; Space for the handle result
  157.             subq    #2,sp
  158.             move.l    a0,-(sp)
  159.             _NewIconSuite                        ; Create a new icon suite
  160.             addq    #2,sp                        ; Ignore the error for now
  161.             move.l    (sp)+,d7                    ; Get the handle to the new icon suite
  162.             move.l    d7,DylanTalkGlobals.dylanTalkIcons(a2)
  163.             
  164.             lea        IconTypeTable,a4            ; Get list of icon types to put in the suite
  165. @loadIconLoop
  166.             move.l    (a4)+,d3                    ; Get an icon type
  167.             bz.s    @registerGestaltSelector    ; Got all the icons
  168.             
  169. ; Get the icon resource
  170.  
  171.             subq    #4,sp
  172.             move.l    d3,-(sp)
  173.             move.w    #kDylanTalkIconID,-(sp)
  174.             _Get1Resource                        ; Get an icon resource
  175.             move.l    (sp)+,d4                    ; Get the handle
  176.             bz.s    @loadIconLoop                ; If no icon of this type, just skip it.
  177.             move.l    d4,-(sp)
  178.             _DetachResource                        ; Detach it so it sticks around
  179.             
  180. ; Add it to the icon suite.
  181.             
  182.             subq    #2,sp
  183.             move.l    d4,-(sp)                    ; Handle to the icon resource
  184.             move.l    d7,-(sp)                    ; Handle to the icon suite
  185.             move.l    d3,-(sp)                    ; Resource type.
  186.             _AddIconToSuite                        ; Add the icon to the icon suite
  187.             addq    #2,sp
  188.             bra.s    @loadIconLoop
  189.  
  190. ; Register our gestalt selector and save our globals
  191.  
  192. @registerGestaltSelector
  193.             lea        DylanTalkGestalt,a1    
  194.             move.l    a3,a0
  195.             _HUnlock
  196.             move.l    a0,2(a1)                    ; Save handle to globals in our gestalt routine
  197.             subq    #2,sp
  198.             move.l    #gestaltDylanTalk,d0        ; DylanTalk selector type
  199.             move.l    a1,a0                        ; A1 still has pointer to Gestalt routine
  200.             _NewGestalt
  201.             addq    #2,sp
  202.  
  203. ; Open the default personality file
  204.  
  205. @openPersonalityFile
  206.             jsr        OpenPersonalityFile
  207.             move.l    (sp)+,TheZone                                    ; Restore the current zone
  208.             
  209.             rts
  210.  
  211. IconTypeTable
  212.             dc.l    Small1BitMask, Small4BitData, Small8BitData, 0
  213.             EndProc
  214.  
  215. ;_________________________________________________________________________________________
  216. ; Gestalt routine
  217.  
  218. DylanTalkGestalt    Proc    Export
  219.             bra.s    GestaltStart
  220. globalHandle
  221.             ds.l    1                            ; Handle to DylanTalk globals stored here
  222. GestaltStart
  223.             move.l    (sp)+,a0                    ; Get return address
  224.             move.l    (sp)+,a1                    ; Get address to stash global handle
  225.             move.l    globalHandle,(a1)
  226.             addq    #4,sp                        ; Pop selector
  227.             clr.w    (sp)                        ; Return noErr
  228.             jmp        (a0)
  229.             EndProc
  230.             
  231. ;_________________________________________________________________________________________
  232. ; SystemTask patch to install DylanTalk menu
  233. ;
  234.  
  235. SystemTaskPatch    Proc    Export
  236.             Import    FillPersonalityMenu
  237.             
  238.             bra.s    PatchStart
  239.             PatchHeader oldSystemTaskPatch
  240.             
  241. PatchStart
  242.             movem.l    a3/a4,-(sp)
  243.                     
  244.             move.l    #gestaltDylanTalk,d0
  245.             _Gestalt                            ; Get our globals
  246.             move.l    (a0),a3
  247.             move.l    DylanTalkGlobals.dylanTalkIcons(a3),a3    ; Get handle to icon suite
  248.             
  249. ; Insert the menu into the SystemMenuList.
  250.             
  251.             move.l    TheZone,-(sp)
  252.             move.l    SysZone,TheZone
  253.             subq    #4,sp
  254.             move.w    #kDylanTalkMenuID,-(sp)
  255.             pea        DylanTalkMenuName
  256.             _NewMenu                            ; Create a new menu
  257.             move.l    (sp),a4                        ; Leave handle for InsertMenu
  258.             clr.w    -(sp)
  259.             move.l    (a4),a0
  260.             move.w    #$0501,menuData(a0)            ; Special signal for icon title.
  261.             move.l    a3,menuData+2(a0)            ; Stuff icon suite handle into menu
  262.             _InsertMenu
  263.             
  264. ; Look for Personality files in the Preferences folder
  265.  
  266.             move.l    a4,-(sp)
  267.             jsr        FillPersonalityMenu
  268.             
  269.             lea        SystemTaskPatch,a0
  270.             move.w    #$4EF9,(a0)                    ; Change branch to jmp to short circuit this patch
  271.             move.l    $6F4,a0                        ; Get cache flush vector
  272.             jsr        (a0)                        ; Flush the caches
  273.             
  274.             move.l    (sp)+,TheZone
  275.             movem.l    (sp)+,a3/a4
  276.             bra.s    SystemTaskPatch
  277. DylanTalkMenuName
  278.             dc.l    'Dylan'
  279.             EndProc
  280.             
  281. ;_________________________________________________________________________________________
  282. ; SystemMenu patch to take care of choices in the DylanTalk menu
  283.  
  284. SystemMenuPatch    Proc    Export
  285.             Export    oldSystemMenu
  286.             Import    ChooseNewPersonality
  287.             Import    DylanTalkAbout
  288.             
  289.             bra.s    PatchStart
  290.             PatchHeader oldSystemMenu
  291.             
  292. PatchStart
  293.             move.l    4(sp),d0                    ; Get menu selection
  294.             swap    d0                            ; Get menu id into low word
  295.             cmp.w    #kDylanTalkMenuID,d0        ; Is it the DylanTalk menu?
  296.             bne.s    oldSystemMenu                ; No.  Get on with SystemMenu
  297.             
  298. ; The user picked something in the DylanTalk menu.
  299.  
  300.             move.l    TheZone,-(sp)
  301.             move.l    SysZone,TheZone
  302.  
  303.             swap    d0
  304.             cmpi.w    #1,d0                        ; See if it’s the about item
  305.             bne.s    @chooseNewPersonality        ; It’s a personality.
  306.             jsr        DylanTalkAbout                ; User picked the about box.
  307.             bra.s    @backToCaller
  308.             
  309. ; Switch to a new personality
  310.  
  311. @chooseNewPersonality
  312.             move.w    d0,-(sp)                    ; Pass item number to ChooseNewPersonality 
  313.             subq    #4,sp
  314.             swap    d0
  315.             move.w    d0,-(sp)
  316.             _GetMHandle                            ; Get handle to DylanTalk menu
  317.             jsr        ChooseNewPersonality
  318. @backToCaller
  319.             move.l    (sp)+,TheZone
  320.             rts
  321.             EndProc
  322.             
  323. ;_________________________________________________________________________________________
  324. ; ShowHide patch to trigger the sound
  325.  
  326. ShowHidePatch    Proc    Export
  327.             Export    oldShowHide
  328.             Import    FindDialogInGlobals
  329.             Import    GetFirstItem
  330.             Import    SpeakStringSounds, KillStringSounds
  331.  
  332.             bra.s    PatchStart
  333.             PatchHeader oldShowHide
  334. PatchStart
  335.             move.l    6(sp),a0                    ; Get pointer to WindowRecord
  336.             cmpi.w    #kDialogKind,windowKind(a0)    ; Is it a dialog?
  337.             bne.s    oldShowHide                    ; No.  Don’t talk.
  338.  
  339. ; Make sure it’s modal
  340.  
  341.             subq    #2,sp
  342.             move.l    a0,-(sp)
  343.             _GetWVariant                        ; Get the window variant
  344.             move.w    (sp)+,d0                    ; Get variant
  345.             cmpi.w    #dBoxProc,d0                ; Is it modal?
  346.             beq.s    @isModal
  347.             cmpi.w    #movableDBoxProc,d0            ; Or a movable modal?
  348.             bne.s    oldShowHide
  349.  
  350. ; This is the right type of window.  Are we showing or hiding a window?
  351.  
  352. @isModal
  353.             tst.b    4(sp)                        ; Non zero for show
  354.             bz.s    @removeDialogFromList        ; This dialog is being hidden.  Remove it from the list
  355.             
  356. ; This dialog is being shown.  Make sure it’s not in the list yet.
  357.  
  358.             move.l    6(sp),a0                    ; Get dialog pointer again
  359.             bsr        FindDialogInGlobals            ; Is it in the list?
  360.             bnz.s    oldShowHide                    ; If it is, we already talked, so don’t talk again.
  361.             
  362. ; This dialog is not in the list.  Stick it in an empty spot
  363.  
  364.             bsr        FindDialogInGlobals
  365.             bnz.s    @foundSlot                    ; If non-zero, we have a place
  366.             pea        #'Ran out of slots'
  367.             _DebugStr
  368.             bra.s    oldShowHide
  369. @foundSlot
  370.             move.l    6(sp),d0
  371.             move.l    d0,(a0)                        ; Save the dialog
  372.             bra.s    FindFirstStaticTextItem        ; Go find the first static string
  373.             
  374. ; This dialog is being hidden.  Remove it from the list if it’s there
  375.  
  376. @removeDialogFromList
  377.             move.l    6(sp),a0                    ; Get dialog pointer
  378.             bsr        FindDialogInGlobals            ; Find it in our globals
  379.             bz.s    oldShowHide                    ; If it’s not there, just go on
  380.             clr.l    (a0)                        ; Otherwise, zap it from our list
  381.             
  382.             move.l    a3,-(sp)
  383.             move.l    #gestaltDylanTalk,d0
  384.             _Gestalt                            ; Get handle to globals
  385.             move.l    a0,a3                        ; Save it
  386.             _HLock                                ; Lock it down
  387.             move.l    (a0),-(sp)                    ; Pass a pointer to our globals
  388.             jsr        KillStringSounds            ; Release memory used for string sounds for this dialog
  389.             move.l    a3,a0
  390.             _HUnlock
  391.             
  392.             move.l    (sp)+,a3
  393.             bra.s    oldShowHide
  394.  
  395. ;_________________________________________________________________________________________
  396. ;            
  397. ; This is a dialog we want to talk.  Gather all the static text items, and parse the first one.
  398. ;
  399.  
  400. FindFirstStaticTextItem
  401.             movem.l    a3/a4,-(sp)
  402.             move.l    d0,a4                        ; Save pointer to dialog
  403.             
  404.             move.l    #gestaltDylanTalk,d0
  405.             _Gestalt                            ; Get handle to globals
  406.             move.l    a0,a3                        ; Save pointer to globals
  407.             _Hlock                                ; lock our globals
  408.                 
  409. ; Reset the string back to nothing for each new dialog
  410.                 
  411.             move.l    (a0),a0
  412.             move.l    DylanTalkGlobals.firstDialogString(a0),a0    ; Reset string
  413.             clr.l    (a0)
  414.             
  415. ; Find the first static text string in this dialog
  416.             
  417.             move.l    a4,-(sp)                    ; Pass the dialog pointer
  418.             jsr        GetFirstItem                ; Find the first static text item
  419.             move.l    (a3),a0                        ; Get pointer to our globals
  420.             move.l    DylanTalkGlobals.firstDialogString(a0),a1
  421.             tst.b    (a1)                        ; Make sure there’s a string
  422.             bz.s    @noStringToSay                ; If not, skip the call to SpeakStringSounds
  423.             
  424. ; Say the text.
  425.             
  426.             move.l    TheZone,-(sp)                ; Save the current zone
  427.             move.l    SysZone,TheZone                ; Do all our work in the system zone
  428.             move.l    a0,-(sp)                    ; Pass pointer to globals
  429.             jsr        SpeakStringSounds            ; Start talking
  430.             move.l    (sp)+,TheZone                ; Restore the current zone
  431. @noStringToSay
  432.             move.l    a3,a0
  433.             _HUnlock                            ; unlock the globals
  434.             
  435.             movem.l    (sp)+,a3/a4
  436.             bra        oldShowHide
  437.             EndProc
  438.  
  439. ;_________________________________________________________________________________________
  440. ; NewDialog tail patch that forces a call to ShowHide if the dialog is becoming visible.
  441. ; FUNCTION    NewDialog(wStorage:   Ptr;                    30(A7)
  442. ;                     boundsRect: Rect;                    26(A7)
  443. ;                     title:      Str255;                22(A7)
  444. ;                     visible:     BOOLEAN;                20(A7)
  445. ;                     theProc:     INTEGER;                18(A7)
  446. ;                     behind:     WindowPtr;             14(A7)
  447. ;                     goAwayFlag: BOOLEAN;                12(A7)
  448. ;                     refCon:     LongInt;                 8(A7)
  449. ;                     items:      Handle): DialogPtr;     4(A7)
  450.  
  451. NewDialogPatch    Proc    Export
  452.             Export    oldNewDialog
  453.             
  454.             bra.s    PatchStart
  455.             PatchHeader oldNewDialog
  456. oldReturnAddress
  457.             jmp        $12345678
  458. PatchStart
  459.             tst.b    20(sp)                        ; is this dialog gonna be visible?
  460.             beq.s    oldNewDialog                ; no skip tail patch
  461.             
  462.             lea        oldReturnAddress,a0            ; save the oldReturnAddress
  463.             move.l    (sp),2(a0)
  464.             
  465.             lea        comeBack,a0                    ; replace it with us! so we
  466.             move.l    a0,(sp)                        ; don't have to repush all the params!
  467.             bra.s    oldNewDialog
  468.  
  469. ; At this point the DialogPtr is on the stack as a return result
  470. ; We pass it to ShowHide and then jmp to the old return address
  471.  
  472. comeBack    
  473.             move.l    (sp),-(sp)                    ; push the window for show hide
  474.             move.b    #1,-(sp)                    ; true means show it
  475.             _ShowHide
  476.             bra.s    oldReturnAddress            ; get outta here…
  477.             EndProc
  478.             
  479. ;_________________________________________________________________________________________
  480. ; HiliteControlPatch
  481. ;
  482. ;    This patch is used to catch keystrokes that hilite buttons (like return for
  483. ; “OK”, and escape for “Cancel”.  I know, I could have patched the modal dialog
  484. ; event filter, but I didn’t feel like it.  This patch is short circuited by
  485. ; TrackControlPatch and DialogSelectPatch, which also hilite buttons, but they
  486. ; care of the speeh.
  487.  
  488. HiliteControlPatch    Proc    Export
  489.             Import    SpeakStringSounds
  490.             Export    oldHiliteControl
  491.             Export    patchIsActive
  492.             
  493.             bra.s    PatchStart
  494.             PatchHeader oldHiliteControl
  495. patchIsActive
  496.             dc.w    1                            ; This patch starts out active
  497. PatchStart
  498.             move.w    patchIsActive,d0            ; Is this patch active?
  499.             bz.s    oldHiliteControl            ; No.
  500.             cmpi.w    #inButton,4(sp)                ; Check the control part that’s being hilited
  501.             bne.s    oldHiliteControl            ; If it’s not highlighting a button, don’t say anything
  502.  
  503. ; It’s a button, or radio control.  Speak it’s title.
  504.  
  505.             movem.l    a2/a3,-(sp)
  506.             move.l    #gestaltDylanTalk,d0
  507.             _Gestalt
  508.             move.l    a0,a3
  509.             _HLock
  510.             move.l    (a3),a0                                        ; Get pointer to our globals
  511.             move.l    DylanTalkGlobals.firstDialogString(a0),a2    ; Get pointer to string to speak
  512.             move.l    4+4+4+2(sp),-(sp)                            ; Get the control handle
  513.             move.l    a2,-(sp)
  514.             _GetCTitle                                        ; Get title of the control
  515.             tst.b    (a2)                                    ; Make sure there’s a title
  516.             bz.s    @doneSpeakingTitle                        ; If not, don’t say anything
  517.             
  518. ; Say the title
  519.  
  520.             move.l    TheZone,-(sp)                            ; Save the current zone
  521.             move.l    SysZone,TheZone                            ; Do all our work in the system zone
  522.             move.l    (a3),a0
  523.             move.w    #-1,DylanTalkGlobals.playingControlSound(a0)    ; Mark that we’re talking about a button    
  524.             move.l    a0,-(sp)                                ; Pass a pointer to the globals
  525.             jsr        SpeakStringSounds                        ; Go say something
  526.             move.l    (sp)+,TheZone                            ; Restore the previous zone
  527. @doneSpeakingTitle
  528.             move.l    a3,a0
  529.             _HUnlock                                        ; Unlock our globals
  530.             movem.l    (sp)+,a2/a3
  531.             bra.s    oldHiliteControl
  532.             EndProc
  533.             
  534. ;_________________________________________________________________________________________
  535. ; TrackControlPatch
  536. ;
  537. ;    If the user lets up outside a button, check box, or radio button, say “Enh.”
  538. ;
  539.  
  540. TrackControlPatch    Proc    Export
  541.             Import    SpeakStringSounds
  542.             Import    patchIsActive
  543.             Export    oldTrackControl
  544.             
  545. TrackControlStack    Record    {RetAddr},Decr
  546. trackResult    ds.w    1
  547. paramBegin    equ        *
  548. theControl    ds.l    1
  549. startPt        ds.l    1
  550. actionProc    ds.l    1
  551. paramSize    equ        paramBegin - *
  552. retAddr        ds.l    1
  553.             EndR
  554.             
  555.             With    TrackControlStack
  556.             bra.s    PatchStart
  557.             PatchHeader oldTrackControl
  558. oldReturnAddress
  559.             jmp        $12345678
  560. savedControl
  561.             ds.l    1
  562.             
  563. PatchStart
  564.             move.l    theControl(sp),a1            ; Get the control handle
  565.             move.l    (a1),a0
  566.             move.l    contrlDefHandle(a0),a0        ; Get CDEF handle
  567.             tst.l    (a0)                        ; See if CDEF is loaded
  568.             bnz.s    @cdefIsLoaded                ; Skip LoadResource if it is
  569.             move.l    a0,-(sp)
  570.             _LoadResource                        ; Load the CDEF into memory
  571. @cdefIsLoaded
  572.             move.l    (a0),a0                        ; Get pointer to CDEF
  573.             tst.w    8(a0)                        ; Make sure it’s CDEF 0, since we’re only doing buttons, and such.
  574.             bnz.s    oldTrackControl                ; If it’s not, skip out of here.
  575.             
  576.             lea        patchIsActive,a0
  577.             clr.w    (a0)                        ; Disable the HiliteControl patch
  578.             lea        oldReturnAddress,a0            ; Get address to save real return address
  579.             addq    #2,a0                        ; Skip past jump
  580.             move.l    (sp)+,(a0)+                    ; Save the return address
  581.             move.l    a1,(a0)                        ; Save the control handle
  582.             bsr.s    oldTrackControl                ; And call TrackControl
  583.             lea        patchIsActive,a0
  584.             move.w    #1,(a0)                        ; Reactivate the HiliteControl patch
  585.             
  586.             move.l    a3,-(sp)                    ; We’ll need an extra register now
  587.             move.l    #gestaltDylanTalk,d0
  588.             _Gestalt                            ; Get our globals
  589.             _HLock                                ; Lock it down
  590.             move.l    a0,a3                        ; Save it
  591.             move.l    (a3),a0                        ; Get pointer to globals
  592.  
  593. ; See where the mouse was let up.
  594.  
  595.             tst.w    4(sp)                        ; Check result
  596.             bnz.s    @dontSayAnything            ; If inside, DialogSelectPatch will do the talking.
  597.             
  598. ; If mouse was outside control, say “Enh.”
  599.  
  600.             move.l    TheZone,-(sp)
  601.             move.l    SysZone,TheZone                ; Do all our work in the system zone
  602.             move.l    DylanTalkGlobals.firstDialogString(a0),a1
  603.             move.l    EnhSoundName,(a1)
  604.             move.l    a0,-(sp)                    ; Pass pointer to globals
  605.             jsr        SpeakStringSounds            ; Say the title
  606.             move.l    (sp)+,TheZone
  607.             move.l    a3,a0
  608.             _HUnlock                            ; Unlock our globals
  609. @dontSayAnything
  610.             move.l    (sp)+,a3                    ; Restore a3
  611.             bra.s    oldReturnAddress            ; And return to the caller
  612.  
  613. EnhSoundName
  614.             dc.l    'Enh'
  615.             EndProc
  616.             
  617. ;_________________________________________________________________________________________
  618. ; DialogSelectPatch
  619. ;
  620. ; Catch hits on buttons, check buttons, and radio controls and speak the title of these
  621. ; controls.
  622. ;
  623.  
  624. DialogSelectPatch    Proc    Export
  625.                     Export    oldDialogSelect
  626.                     Import    SpeakStringSounds
  627.                     Import    patchIsActive
  628.                     
  629. DialogSelectStack    Record    {A6Link},Decr
  630. result                ds.w    1
  631. paramBegin            equ        *
  632. theEvent            ds.l    1
  633. theDialog            ds.l    1
  634. itemHit                ds.l    1
  635. paramSize            equ        paramBegin - *
  636. retAddr                ds.l    1
  637. A6Link                ds.l    1
  638. itemType            ds.w    1
  639. itemHandle            ds.l    1
  640. itemBox                ds.l    2
  641. localSize            equ        *
  642.                     EndR
  643.  
  644.             With    DialogSelectStack
  645.             bra.s    PatchStart
  646.             PatchHeader oldDialogSelect
  647. PatchStart
  648.             link    a6,#localSize
  649.  
  650. ; First, call DialogSelect to let the Dialog Manager take care of the event.
  651.  
  652.             lea        patchIsActive,a0
  653.             clr.w    (a0)                                    ; Disable HiliteControl patch
  654.             clr.w    result(a6)
  655.             subq    #2,sp
  656.             move.l    theEvent(a6),-(sp)
  657.             move.l    theDialog(a6),-(sp)
  658.             move.l    itemHit(a6),-(sp)
  659.             bsr.s    oldDialogSelect
  660.             lea        patchIsActive,a0
  661.             move.w    #1,(a0)                                    ; Reenable HiliteControl patch
  662.             move.b    (sp)+,d0
  663.             move.b    d0,result(a6)                            ; Pass the result back.
  664.             bz.s    @exitDialogSelectPatch                    ; Just exit if no item was hit
  665.             
  666. ; Find out the item type of the item that was hit.
  667.  
  668.             move.l    theDialog(a6),a0
  669.             move.l    (a0),-(sp)                                ; Dialog that was hit
  670.             move.l    itemHit(a6),a0
  671.             move.w    (a0),-(sp)                                ; Item number that was hit
  672.             pea        itemType(a6)
  673.             pea        itemHandle(a6)
  674.             pea        itemBox(a6)
  675.             _GetDItem                                        ; Get info about this item
  676.             
  677.             cmpi.w    #ctrlItem,itemType(a6)
  678.             blt.s    @exitDialogSelectPatch
  679.             cmpi.w    #ctrlItem+radCtrl,itemType(a6)            ; Is it a button type?
  680.             bgt.s    @exitDialogSelectPatch                    ; If it’s not, just exit.
  681.             
  682. ; It’s a button, radio control, or check box.  Speak it’s title.
  683.  
  684.             movem.l    a2/a3,-(sp)
  685.             move.l    #gestaltDylanTalk,d0
  686.             _Gestalt
  687.             move.l    a0,a3
  688.             _HLock
  689.             move.l    (a3),a0                                    ; Get pointer to our globals
  690.             move.l    DylanTalkGlobals.firstDialogString(a0),a2 ; Get pointer to string to speak
  691.             move.l    itemHandle(a6),-(sp)
  692.             move.l    a2,-(sp)
  693.             _GetCTitle                                        ; Get title of the control
  694.             tst.b    (a2)                                    ; Make sure there’s a title
  695.             bz.s    @doneSpeakingTitle                        ; If not, don’t say anything
  696.             
  697. ; Say the title
  698.  
  699.             move.l    TheZone,-(sp)                            ; Save the current zone
  700.             move.l    SysZone,TheZone                            ; Do all our work in the system zone
  701.             move.l    (a3),a0
  702.             move.w    #-1,DylanTalkGlobals.playingControlSound(a0)    ; Mark that we’re talking about a button    
  703.             move.l    a0,-(sp)                                ; Pass a pointer to the globals
  704.             jsr        SpeakStringSounds                        ; Go say something
  705.             move.l    (sp)+,TheZone                            ; Restore the previous zone
  706. @doneSpeakingTitle
  707.             move.l    a3,a0
  708.             _HUnlock                                        ; Unlock our globals
  709.             movem.l    (sp)+,a2/a3
  710. @exitDialogSelectPatch
  711.             unlk    a6
  712.             move.l    (sp)+,a0
  713.             add.w    #paramSize,sp
  714.             jmp        (a0)
  715.             EndProc
  716.             
  717. ;_________________________________________________________________________________________
  718. ; FindCharacterRun(theString: StringPtr; offset: integer; stringToSpeak: StringPtr, Var runStartOffset: integer): Boolean;
  719. ;
  720. ; This routine picks out a run of alpha numeric characters from a string, starting at
  721. ; offset.  If the end of the string is encountered before a run is found, false is
  722. ; returned.  If a run is found, a copy of the run is made into stringToSpeak, and the
  723. ; offset at which this run began are returned, and the function result is true.
  724. ;
  725.  
  726. FindCharacterRun    Proc    Export
  727. FindCharacterRunStack    Record    {A6Link},Decr
  728. result            ds.w    1                            ; Whether a run was found before the end of the string
  729. paramBegin        equ        *
  730. theString        ds.l    1                            ; String to search
  731. offset            ds.w    1                            ; Offset into string to start searching from
  732. stringToSpeak    ds.l    1                            ; String to copy run into.
  733. runStartOffset    ds.l    1
  734. paramSize        equ        paramBegin - *
  735. retAddr            ds.l    1
  736. A6Link            ds.l    1
  737. isNumber        ds.w    1
  738. localSize        equ        *
  739.                 EndR
  740.             
  741.             With    FindCharacterRunStack
  742.             link    a6,#localSize
  743.             movem.l    d2/d3,-(sp)
  744.             
  745.             move.w    #-1,result(a6)                ; Assume we’ll find a run
  746.             move.l    theString(a6),a0            ; Get pointer to string
  747.             moveq    #0,d0
  748.             moveq    #0,d3
  749.             move.b    (a0)+,d0                    ; Get length of string
  750.             subq    #1,d0
  751.             move.w    offset(a6),d1
  752.             sub.w    d1,d0                        ; There will be offset less characters to run through before the end of the string
  753.             ble.s    @endOfString                ; If this knocks us to the end, bail
  754.             add.w    d1,a0                        ; Start at the desired offset
  755.             move.l    stringToSpeak(a6),a1        ; Get string to put run into
  756.             clr.b    (a1)+                        ; Assum zero length string to start with
  757.             
  758.             clr.w    isNumber(a6)
  759.             moveq    #0,d1                        ; Count of run length
  760.             moveq    #0,d2
  761. @nextCharacter
  762.             move.b    (a0)+,d2                    ; Get a character
  763.             cmpi.b    #'’',d2                        ; Special curly apostrophe check
  764.             beq.s    @isAlphanumeric
  765.             tst.w    isNumber(a6)                ; Are we in a number run?
  766.             bz.s    @skipCommaCheck                ; If not, don’t bother with the comma
  767.             cmpi.b    #',',d2                        ; Check for commas in a number run
  768.             beq.s    @isAlphanumeric                ; Count the comma as part of the run for number runs.
  769. @skipCommaCheck
  770.             cmpi.b    #'.',d2                        ; Do some special checking for a period
  771.             bne.s    @isNotPeriod
  772. @checkEndOfSentence
  773.             tst.l    d1                            ; See if we’ve started a run yet.
  774.             bz.s    @isAlphanumeric                ; If we’re just starting play some silence.
  775.             cmpi.b    #' ',(a0)                    ; See if the following character is a space
  776.             beq.s    @gotToEnd                    ; Pick up the period next time.
  777.             tst.w    isNumber(a6)                ; If we’re in the middle of a word, see if the word is a number
  778.             bnz.s    @isAlphanumeric                ; If we’re in a number, treat the period as part of the number
  779.             bra.s    @notAlphanumeric            ; Otherwise, ignore the period.
  780. @isNotPeriod
  781.             cmpi.b    #'0',d2                        ; Check the lower numeric bound
  782.             blt.s    @notAlphanumeric            ; It’s not in range
  783.             cmpi.b    #'9',d2                        ; Check the upper numeric bound
  784.             bgt.s    @notANumber                    ; It’s not a number.  Try a letter
  785.             move.w    #1,isNumber(a6)                ; Set our flag that says we’re doing a number run.
  786.             bra.s    @isAlphanumeric                ; And remember this digit.
  787. @notANumber
  788.             tst.w    isNumber(a6)                ; Are we in the middle of a number run?
  789.             bnz.s    @gotToEnd                    ; If we are, stop right here.
  790.             cmpi.b    #'A',d2                        ; Check the lower alphabetic bound
  791.             blt.s    @notAlphanumeric            ; It’s not a letter
  792.             cmpi.b    #'Z',d2                        ; Check the upper alphabetic bound
  793.             ble.s    @isAlphanumeric                ; It’s a letter
  794. @notAlphanumeric
  795.             tst.l    d1                            ; Check the current run length
  796.             bnz.s    @gotToEnd                    ; Otherwise, we’ve found the end of the run
  797.             addq    #1,d3                        ; Still looking for the start of a run.  Bump the offset to the run’s start.
  798.             bra.s    @getNextCharacter            ; If it’s zero, we’re still looking for the start of a run
  799. @isAlphanumeric
  800.             move.b    d2,(a1)+                    ; Copy the character into speech buffer
  801.             addq    #1,d1                        ; Increment run length at any rate
  802. @getNextCharacter
  803.             dbra    d0,@nextCharacter            ; Check the next character
  804.             
  805. ; Got to the end of the string.  See if we found a run.
  806.  
  807.             tst.l    d1                            ; Check run length
  808.             bnz.s    @gotToEnd                    ; Found a run
  809. @endOfString
  810.             clr.w    result(a6)                    ; Say we didn’t find a run
  811. @gotToEnd
  812.             move.l    stringToSpeak(a6),a0        ; Get string to speak.
  813.             move.b    d1,(a0)                        ; Stuff length of run into string
  814.             move.l    runStartOffset(a6),a0
  815.             move.w    d3,(a0)                        ; Pass back where the run started.
  816.             
  817.             move.l    (sp)+,d2
  818.             unlk    a6
  819.             move.l    (sp)+,a0
  820.             add.w    #paramSize,sp                ; Pop parameters
  821.             jmp        (a0)
  822.  
  823. ;_________________________________________________________________________________________
  824. ; This routine converts all lower case alphabetic characters into uppercase in a string.
  825. ;
  826.  
  827. ConvertStringToUpperCase    Proc    Export
  828.             move.l    (sp)+,a0                    ; Get return address
  829.             move.l    (sp)+,a1                    ; Get string pointer
  830.             move.l    a0,-(sp)                    ; Put return address back on stack
  831.             
  832.             moveq    #0,d0
  833.             moveq    #0,d1
  834.             move.b    (a1)+,d0                    ; Get length of string
  835.             bz.s    @exitConvertString            ; Bail if no string.
  836.             subq    #1,d0                        ; Zero base it.
  837. @conversionLoop
  838.             move.b    (a1)+,d1                    ; Get a character
  839.             cmpi.b    #'a',d1                        ; Is it lower case?
  840.             blt.s    @nextCharacter                ; No.  Leave it alone
  841.             subi.w    #'a'-'A',d1                    ; Convert it to uppercase
  842.             move.b    d1,-1(a1)
  843. @nextCharacter
  844.             dbra    d0,@conversionLoop
  845. @exitConvertString
  846.             rts
  847.             EndProc
  848.             
  849. ;_________________________________________________________________________________________
  850. ; Look for a given dialog pointer in our globals.
  851. ;
  852. ; Parameters:
  853. ;    A0 -> Pointer to dialog to search for
  854. ;    A0 <- Address of dialog in global handle, or NIL if not found
  855. ;
  856.  
  857. FindDialogInGlobals    Proc    Export
  858.             move.l    d7,-(sp)
  859.             move.l    a0,d7                        ; Keep dialog pointer in a static register
  860.             move.l    #gestaltDylanTalk,d0
  861.             _Gestalt                            ; Get handle to globals
  862.             move.l    (a0),a0
  863.                 
  864. ; Search for the given dialog pointer in globals
  865.  
  866.             moveq    #kNumCachedDialogs - 1,d0
  867. @searchForDialog
  868.             cmp.l    (a0)+,d7                    ; Is this one it?
  869.             beq.s    @foundDialog
  870.             dbra    d0,@searchForDialog
  871.             move.l    #4,a0                        ; Didn’t find the dialog
  872. @foundDialog
  873.             subq    #4,a0                        ; Back up to the right pointer
  874.             move.l    (sp)+,d7                    ; Restore A4
  875.             move.l    a0,d0                        ; Set the condition code
  876.             rts
  877.             EndProc
  878.  
  879. ;_________________________________________________________________________________________
  880. ; Given a dialog pointer, find the first static text item.
  881.  
  882. GetFirstItem    Proc    Export
  883.                 Import    MungeInParamText
  884.                 
  885. GetFirstItemStackFrame    Record    {A6Link},Decr
  886. paramBegin            equ        *
  887. theDialog            ds.l    1
  888. paramSize            equ        paramBegin - *
  889. retAddr                ds.l    1
  890. A6Link                ds.l    1
  891. potentialFirstItem    ds.l    1
  892. itemType            ds.w    1
  893. itemHandle            ds.l    1
  894. itemBox                ds.l    2
  895. itemString            ds.b    256
  896. localSize            equ        *
  897.                     EndR
  898.                 
  899.             With    GetFirstItemStackFrame
  900.             link    a6,#localSize
  901.             movem.l    d6-d7/a2-a4,-(sp)
  902.             
  903.             move.l    #gestaltDylanTalk,d0
  904.             _Gestalt
  905.             move.l    (a0),a0                        ; Get handle to globals
  906.             move.l    DylanTalkGlobals.firstDialogString(a0),d6     ; Get pointer to space to save string in.
  907.             
  908.             move.l    theDialog(a6),a4            ; Keep dialog in a register
  909.             move.l    items(a4),a0                ; Get items handle
  910.             move.l    (a0),a0
  911.             move.w    (a0),d7                        ; Get count of items - 1
  912.             move.l    #$7FFF7FFF,potentialFirstItem(a6)    ; Start with a point that’s way down and to the right
  913.             
  914. ; Loop through all the items in the dialog, looking for static text items
  915.  
  916. @itemLoop
  917.             move.l    a4,-(sp)                    ; Dialog record
  918.             move.w    d7,-(sp)
  919.             add.w    #1,(sp)                        ; Item number is one based
  920.             pea        itemType(a6)
  921.             pea        itemHandle(a6)
  922.             pea        itemBox(a6)
  923.             _GetDItem
  924.             
  925. ; See if it’s a static text item
  926.  
  927.             move.w    itemType(a6),d0                ; Get the type
  928.             andi.w    #$7F,d0                        ; Mask off disabled bit
  929.             cmpi.w    #statText,d0                ; Is it a static text item?
  930.             bne.s    @nextItem
  931.  
  932. ; It’s a static text item.  Make sure it’s in the visible portion of the dialog.
  933.  
  934.             subq    #2,sp
  935.             pea        portRect(a4)                ; Compare the dialog rectangle
  936.             pea        itemBox(a6)                    ; Against the rectangle for this item
  937.             pea        itemBox(a6)                    ; And put the result in the item rectangle.  If the item is visible, this will remain unchanged.
  938.             _SectRect                            ; See if the two rectangles intersect.
  939.             tst.b    (sp)+                        ; Is the text item visible?
  940.             bz.s    @nextItem                    ; If not, then don’t bother.
  941.  
  942. ; Check the position of this item to see if it’s closer to the upper left of the dialog than previous text items.
  943.  
  944.             move.l    potentialFirstItem(a6),d0    ; Get our candidate string up until now
  945.             swap    d0                            ; Get top into low word
  946.             cmp.w    itemBox(a6),d0                ; Is the new one closer to the top of the dialog?
  947.             blt.s    @nextItem                    ; If not, skip it.
  948.             bne.s    @saveItemPosition            ; If this text item is higher up, then go get the string
  949.             swap    d0                            ; If it’s at the same level as the previous one, check the left edge
  950.             
  951. ; Dean’s architectural integrity kicks in and tells him to look at the system justification
  952. ; to decide which side of the dialog is the front.
  953.  
  954.             tst.b    TESysJust+1                    ; Check the low byte like everyone else in the world
  955.             bnz.s    @isRightJustified
  956.             cmp.w    itemBox+left(a6),d0            ; Is the new one closer to the left of the dialog?
  957.             blt.s    @nextItem                    ; If the new one is further to the right, skip it.
  958. @saveItemPosition
  959.             move.l    itemBox(a6),potentialFirstItem(a6)    ; This is the new candidate.  Remember it’s coordinates
  960.             bra.s    @newCandidate
  961.             
  962. @isRightJustified
  963.             cmp.w    itemBox+right(a6),d0
  964.             bgt.s    @nextItem
  965.             move.w    itemBox(a6),potentialFirstItem(a6)
  966.             move.w    itemBox+right,potentialFirstItem+left(a6)
  967.             
  968. ; Get the string
  969.  
  970. @newCandidate
  971.             move.l    itemHandle(a6),-(sp)
  972.             pea        itemString(a6)
  973.             _GetIText
  974.             
  975.             subq    #4,sp
  976.             pea        itemString(a6)
  977.             bsr        MungeInParamText            ; Do ParamText subsitution
  978.             move.l    (sp)+,a0                    ; Get handle to munged text
  979.  
  980. ; Filled in all the subsitutions.  Save the string
  981.  
  982.             move.l    a0,a2                        ; Save the handle
  983.             move.l    (a0),a0
  984.             move.l    d6,a1                        ; Get pointer to previous string
  985.             moveq    #0,d0
  986.             move.b    (a0),d0
  987.             addq    #1,d0
  988.             _BlockMove                            ; Copy in the new string
  989.             move.l    a2,a0
  990.             _DisposeHandle                        ; Done with the munged string
  991. @nextItem
  992.             dbra    d7,@itemLoop
  993.  
  994.             movem.l    (sp)+,d6-d7/a2-a4
  995.             unlk    a6
  996.             move.l    (sp)+,a0
  997.             addq    #paramSize,sp
  998.             jmp        (a0)
  999.             EndProc
  1000.  
  1001. ;_________________________________________________________________________________________
  1002. ; Given a pointer to a static text string, convert it to a string handle, then munge
  1003. ; in the paramtext strings in low memory.  Pass back the handle to the munged string.
  1004.  
  1005. MungeInParamText    Proc    Export
  1006. MungeInParamTextStack    Record    {A6Link},Decr
  1007. mungedTextHandle    ds.l    1
  1008. paramBegin            equ        *
  1009. stringToMunge        ds.l    1
  1010. paramSize            equ        paramBegin - *
  1011. retAddr                ds.l    1
  1012. A6Link                ds.l    1
  1013. currentCaret        ds.w    1                    ; Holder for “^[0-3]”
  1014. localSize            equ        *
  1015.                     EndR
  1016.                     
  1017.             With    MungeInParamTextStack
  1018.             link    a6,#localSize
  1019.             movem.l    d7/a2-a4,-(sp)
  1020.             
  1021.             move.l    stringToMunge(a6),a0        ; Pointer to string to munge
  1022.             moveq    #0,d0
  1023.             move.b    (a0),d0                        ; Get length
  1024.             addq    #1,d0                        ; Include length byte
  1025.             _NewHandle ,Sys                        ; Make a handle for munging
  1026.             move.l    a0,a4                        ; Keep it around
  1027.             move.l    (a0),a1
  1028.             move.l    stringToMunge(a6),a0
  1029.             move.b    (a0),d0
  1030.             addq    #1,d0
  1031.             _BlockMove                            ; Copy the string into the handle
  1032.             
  1033.             moveq    #3,d7                        ; Four paramtext strings
  1034.             lea        DAStrings,a2                ; Get address of paramtext strings
  1035.             move.w    #'^0',currentCaret(a6)        ; Start with ^0
  1036. @mungerLoop
  1037.             move.l    (a2),d0                        ; Get a replacement string
  1038.             bz.s    @noReplacementString
  1039.             move.l    d0,a0
  1040.             _HLock                                ; Lock down the replacement string
  1041.             
  1042.             move.l    (a0),a3                        ; Pointer to replacement string
  1043.             moveq    #0,d0
  1044.             move.b    (a3),d0                        ; Get length byte
  1045.             addq    #1,a3                        ; Skip length
  1046. @lookForAnotherCaret
  1047.             subq    #4,sp
  1048.             move.l    a4,-(sp)                    ; Handle to munge
  1049.             clr.l    -(sp)                        ; Offset to start munging at
  1050.             pea        currentCaret(a6)            ; String to replace
  1051.             move.l    #2,-(sp)                    ; It’s two bytes long
  1052.             move.l    a3,-(sp)                    ; Replacement string
  1053.             move.l    d0,-(sp)                    ; It’s length
  1054.             _Munger
  1055.             tst.l    (sp)+                        ; See if it found the caret
  1056.             bge.s    @lookForAnotherCaret        ; Make sure there are no other occurances of
  1057.             
  1058.             move.l    (a2)+,a0
  1059.             _HUnlock                            ; Unlock the replacement string
  1060.             bra.s    @nextSubsitution
  1061. @noReplacementString
  1062.             addq    #4,a2
  1063. @nextSubsitution
  1064.             add.b    #1,currentCaret+1(a6)        ; Next ^ character
  1065.             dbra    d7,@mungerLoop
  1066.             
  1067.             move.l    a4,a0
  1068.             _GetHandleSize                        ; Find out length of munged string
  1069.             move.l    (a0),a0
  1070.             move.b    d0,(a0)                        ; Store the new length
  1071.             move.l    a4,mungedTextHandle(a6)        ; Pass back the result
  1072.             
  1073.             movem.l    (sp)+,d7/a2-a4
  1074.             unlk    a6
  1075.             move.l    (sp)+,a0                    ; Get return address
  1076.             addq    #4,sp
  1077.             jmp        (a0)
  1078.             EndProc
  1079.             End